<?php
/**
 * 控制器
 * 
 * 一个用户访问的控制器需要继承该类，并添加一组doXxx()方法作为动作（action）(<em>app/controllers/my.php</em>)：
 * <code>
 * class MyController extends IController {
 * 	function doIndex() {
 * 		echo "i am index";
 * 	}
 * 
 * 	function doOther() {
 * 		echo "i am other action";
 * 	}
 * }
 * </code>
 * 
 * 如果IController的子类要覆盖某些方法，需要在子类方法中调用 parent::xxx ，否则可能会有意想不到的错误发生，一个比较好的例子是：
 * <code>
 *  function _init() {
 * 		parent::_init();
 * 
 * 		//我的自定义的代码
 *  }
 * </code>
 *
 * @since 1.0
 * @package if
 * @subpackage controller
 */
abstract class IController extends IObject {
	/**
	 * Enter description here...
	 *
	 * @var IAction
	 */
	private $action;
	private $params = array();
	private $filters = array();
	private static $controllerKeyword = "controller";
	private static $actionKeyword = "action";
	private static $defaultAction = "index";	
	/**
	 * Enter description here...
	 *
	 * @var IRouter
	 */
	private $router;
	private $name;
	
	/**
	 * Enter description here...
	 *
	 * @var IContext
	 */
	protected $context;
	
	/**
	 * Enter description here...
	 *
	 * @var ILocale
	 */
	private $locale;
	
	private $errors = array();
	
	private $file;
	
	/**
	 * 表单对象
	 *
	 * @var UIForm
	 * @since 1.1.0
	 */
	private $form;
	
	/**
	 * VIEW路径
	 *
	 * @var string
	 * @since 1.1.2
	 */
	private $viewDirectory;
	
	/**
	 * 构造
	 *
	 * @since 1.0
	 */
	public final function __construct() {
		//检查IApplication是否启动
		if(!IApplication::started()) {
			IApplication::start(false);
		}
		
		$this->context = IContext::singleton();
		//$this->locale = $this->locale();
		
		//过滤器
		$filters = o("@controller.filters");
		if (is_array($filters)) {
			$this->filters = if_array_sort($filters, "order", false);//按order字段排序，1.1.2
		}
		
		//初始化
		$this->_init();
	}
	
	/**
	 * 设置要执行的动作名
	 *
	 * @param string $action 动作名
	 * @since 1.0
	 */
	function setAction($action) {
		$this->action = new IAction($action);
	}
	
	/**
	 * 取得请求的上下文
	 *
	 * @return IContext
	 * @since 1.0
	 */
	function context() {
		return $this->context;
	}
	
	/**
	 * 取得请求对象
	 *
	 * @return IRequest
	 * @since 1.0
	 */
	function request() {
		return $this->context->request();
	}
	
	/**
	 * 取得刚刚执行的动作
	 *
	 * @return IAction
	 * @since 1.0
	 */
	function action() {
		if (!$this->action) {
			$action = x(self::$actionKeyword);
			if ($action) {
				$this->action = new IAction($action);
			}
			else {
				$this->action = new IAction(self::$defaultAction);
			}
		}
		return $this->action;
	}
	
	/**
	 * 添加一组参数
	 *
	 * @param array $params 参数名值对
	 * @since 1.0
	 */
	function addParams(array $params) {
		$this->params = array_merge($this->params, $params);
	}
	
	/**
	 * 添加参数
	 *
	 * @param string $param 参数名
	 * @param mixed $value 参数值
	 * @since 1.0
	 */
	function addParam($param, $value) {
		$this->params[$param] = $value;
	}
	
	/**
	 * 取得控制器参数值
	 *
	 * @param string $name 参数名
	 * @return mixed
	 * @since 1.0
	 */
	function param($name) {
		return array_key_exists($name, $this->params)?$this->params[$name]:null;
	}
	
	/**
	 * 取得所有参数
	 *
	 * @return array
	 * @since 1.0
	 */
	function params() {
		return $this->params;
	}
	
	/**
	 * 添加过滤器
	 *
	 * @param string $filter 过滤器类
	 * @param string|array $actions 要过滤的动作
	 * @since 1.0
	 */
	function addFilter($filter, $actions = null) {
		if (!is_subclass_of($filter, "IActionFilter")) {
			throw new IException("action filter '{$filter}' must be inherited from IActionFilter", 0x100003);
		}
		if (!$actions) {
			$actions = "*";
		}
		$this->filters[] = array("filter" => $filter, "actions" => $actions);
	}
	
	/**
	 * 根据动作名查找对应的过滤器
	 *
	 * @param IAction $action 动作
	 * @return array
	 * @since 1.0
	 */
	private function _filtersByAction($action) {
		$filters = array();
		foreach ($this->filters as $filter) {
			if ($action->match($filter["actions"])) {
				$filterClass = $filter["filter"];
				$filters[] = $filterClass;
			}
		}
		return $filters;
	}
	
	/**
	 * 执行动作
	 *
	 * @since 1.0
	 */
	function exec() {	
		//设置常量
		if (!defined("__CONTROLLER__")) {
			define("__CONTROLLER__", $this->name());
			define("__ACTION__", $this->action()->name());
		}
		
		//检查action是否存在
		$this->action = $this->action();
		$controller = get_class($this);
		$actionName = $this->action->name();
		
		
		//处理特殊属性
		$vars = $this->attrs();
		if (isset($vars["_filter"])) {
			$filters = $vars["_filter"];
			if (is_string($filters) && $filters != "") {
				$this->addFilter($filters);
			}
			else if (is_array($filters)) {
				foreach ($filters as $key=>$filter) {
					if (is_string($key)) {// "IXxxFilter" => "add,delete"
						$this->addFilter($key, $filter);
					}
					else if (is_string($filter) && $filter != "") {//"IXxxFilter"
						$this->addFilter($filter);
					}
				}
			}
		}
		
		//过滤器
		$filters = $this->_filtersByAction($this->action);
		foreach ($filters as $filter) {
			$filterObj = IRegistry::singleton()->single($filter);

			//是否有参数 v1.1.0
			if (isset($vars["_filterParam"]) && is_array($vars["_filterParam"]) && isset($vars["_filterParam"][$filter]) && is_array($vars["_filterParam"][$filter])) {
				$filterObj->setParam($vars["_filterParam"][$filter]);
			}
			if ($filterObj->_before($this) === false) {
				$this->_destroy();
				return;
			}
		}
		
		//执行_before
		$this->_before();
		
		//加载form v1.1.0
 		$this->form = UIForm::initFromCredit();
		
		//执行action
		$method = $this->action->method();//action对应的方法
		if (!method_exists($this, $method)) {
			//not found
			if (method_exists($this, "_notFound")) {
				$method = "_notFound";
			}
			else {
				throw new IActionNotFoundException("can't find action '{$actionName}' in controller '{$controller}'", 0x100038);
			}
		}
		$ret = $this->$method();
		$this->action->setRet($ret);
		
		//执行_after
		$this->_after();
		
		//过滤器
		$filters = array_reverse($filters);//将$filters的顺序倒置
		foreach ($filters as $filter) {
			$filterObj = IRegistry::singleton()->single($filter);
			if ($filterObj->_after($this) === false) {
				$this->_destroy();
				return;
			}
		}
		
		//结束
		$this->_destroy();
	}
	
	/**
	 * 设置控制器关键字
	 *
	 * @param string $controllerKeyword 控制器关键字
	 * @since 1.0
	 */
	static function setControllerKeyword($controllerKeyword) {
		self::$controllerKeyword = $controllerKeyword;
	}
	
	/**
	 * 设置动作关键字
	 *
	 * @param string $actionKeyword 动作关键字
	 * @since 1.0
	 */
	static function setActionKeyword($actionKeyword) {
		self::$actionKeyword = $actionKeyword;
	}
	
	/**
	 * 取得控制器标识关键字
	 *
	 * @return string
	 * @since 1.0
	 */
	static function controllerKeyword() {
		return self::$controllerKeyword;
	}
	
	/**
	 * 取得动作标识关键字
	 *
	 * @return string
	 * @since 1.0
	 */
	static function actionKeyword() {
		return self::$actionKeyword;
	}
	
	/**
	 * 设置默认的动作名
	 *
	 * @param string $default 动作名
	 * @since 1.0
	 */
	static function setDefaultAction($default) {
		self::$defaultAction = $default;
	}
	
	/**
	 * 取得默认的动作名
	 *
	 * @return string
	 * @since 1.0
	 */
	static function defaultAction() {
		return self::$defaultAction;
	}
	
	/**
	 * 根据名称查找控制器类
	 *
	 * @param string $name 名称
	 * @return string|null
	 * @since 1.0
	 */
	static function findByName($name) {
		$pieces = explode(".", $name);
		$useful = array();
		if (empty($pieces)) {
			return null;
		}
		$controller = array_pop($pieces);
		$class = ucfirst(if_name_to_java($controller)) . "Controller";
		foreach ($pieces as $piece) {
			if (!preg_match("/^[\\w\\-]+$/", $piece)) {
				return null;
			}
			$useful[] = $piece;
		}
		if (!empty($useful)) {
			$class = implode(".", $useful) . "." . $class;
		}
		return $class;
	}
	
	/**
	 * 根据名称建立控制器对象
	 *
	 * @param string $name 控制器名
	 * @return IController
	 * @since 1.0
	 */
	static function objByName($name) {
		$obj = null;
		$controllerName = self::findByName($name);
		$realClassName = if_class_name($controllerName);
		$controllerDirectory = IApplication::singleton()->directory("controllers");
		$directories = IApplication::singleton()->directory("userControllers");
		array_unshift($directories, $controllerDirectory);
		
		if (class_exists($realClassName, false)) {
			$obj = new $realClassName();
		}
		
		//查找
		if (!$obj) {
			foreach($directories as $directory) {
				$files = import(if_name_to_c($name), false, $directory);
				if (class_exists($realClassName, false)) {
					$file = array_shift($files);
					$obj = new $realClassName();
					$obj->file = $file;
					if (preg_match("/^(.+)(\\\|\\/)_deploy(\\\|\\/)/", $file, $match)) {
						$obj->setViewDirectory($match[0] . "views/%{locale}/templates");
					}
					break;
				}
			}
		}
		
		//再次查找
		if (!$obj) {
			foreach($directories as $directory) {
				$files = import($controllerName, false, $directory);
				if (class_exists($realClassName, false)) {
					$file = array_shift($files);
					$obj = new $realClassName();
					$obj->file = $file;
					if (preg_match("/^(.+)(\\\|\\/)_deploy(\\\|\\/)/", $file, $match)) {
						$obj->setViewDirectory($match[0] . "views/%{locale}/templates");
					}
					break;
				}
			}
		}
		
		return $obj;
	}
	
	/**
	 * 设置路由
	 *
	 * @param IRouter $router 路由
	 * @since 1.0
	 */
	function setRouter(IRouter $router) {
		$this->router = $router;
	}
	
	/**
	 * 设置控制器名
	 *
	 * @param string $name 名称
	 * @since 1.0
	 */
	function setName($name) {
		$this->name = $name;
	}
	
	/**
	 * 取得控制器名
	 * 
	 * 从v1.1.2以后，如果你是手工调用控制器的，那么控制器名就是类的名称：
	 * <code>
	 * class ThisIsMyController extends IController {
	 * 	function doIndex() {
	 * 
	 * 	}
	 * }
	 * 
	 * $controller = new ThisIsMyController();
	 * $controller->exec();
	 * 
	 * p($controller->name()); // this_is_my
	 * </code>
	 *
	 * @return string
	 * @since 1.0
	 */
	function name() {
		if (if_empty($this->name)) {
			$this->name = if_name_to_c(preg_replace("/controller$/i", "", get_class($this)));
		}
		return $this->name;
	}
	
	/**
	 * 取得一个控制器和动作对应的路径
	 * 
	 * - $this->path("index")
	 * - $this->path("login", "index")
	 * - $this->path("login.index")
	 *
	 * @param string $controllerOrActionName 控制器或动作名
	 * @param string $actionName 动作名
	 * @param array|null $params 参数
	 * @param boolean $alias 是否计算省略的形式
	 * @return string
	 * @since 1.0
	 */
	function path($controllerOrActionName = null, $actionName = null, array $params = null, $alias = false) {
		$_controller = $this->name;
		$_action = null;
		if ($controllerOrActionName && $actionName) {
			$_controller = $controllerOrActionName;
			$_action = $actionName;
		}
		else if (!$controllerOrActionName && !$actionName) {
			$_action = $this->action->name();
		}
		else {
			$_action = $actionName ? $actionName : $controllerOrActionName;
			if (strstr($_action, ".")) {
				$lastDot = strrpos($_action, ".");
				$_controller = substr($_action, 0, $lastDot);
				$_action = substr($_action, $lastDot + 1);
			}
		}
		return $this->router->path($this->_parseControllerPath($this->name(), $_controller), $_action, $params, $alias);
	}
	
	/**
	 * 分析控制器中的[parent]和[self]
	 * 
	 * - _parseControllerPath("a.b.c.d", "[parent].[parent]") => a.b
	 *
	 * @param string $baseController
	 * @param string $path
	 * @since 1.0
	 */
	private function _parseControllerPath($baseController, $path) {
		$path = str_replace("[self]", $this->name(), $path);
		$pieces = explode(".", $baseController);
		preg_match_all("/\[parent\]/", $path, $match);
		$count = count($match[0]);
		if ($count == 0) {
			return $path;
		}
		$pieces = array_slice($pieces, 0, count($pieces) - $count);
		return trim(implode(".", $pieces) . "." . str_replace("[parent].", "", $path), ".");
	}
	
	/**
	 * 调用未定义属性时激活的方法
	 * 
	 * 不允许子类覆写此方法
	 *
	 * @param string $varname 调用的属性名
	 * @return mixed
	 */
	public final function __get($varname) {
		if (IApplication::hasModule("activerecord") && preg_match("/^(.+)Query$/i", $varname, $match)) {
			return IDAO::query($match[1]);		
		}
		try {
			return IReflection::property($this, $varname);
		}
		catch (IException $e) {
			return $this->$varname = new IEmptyObject();
		}
	}
	
	/**
	 * 跳转到新的URL
	 *
	 * @param string $url URL
	 * @since 1.0
	 */
	function redirectUrl($url) {
		IResponse::singleton()->redirect($url);
	}
	
	/**
	 * 跳转到新的action上
	 *
	 * @param string $controllerOrActionName 控制器或者action名称
	 * @param string $actionName action名称
	 * @param array $params 参数
	 * @since 1.0
	 */
	function redirect($controllerOrActionName, $actionName = null, array $params = null) {
		if (strstr($controllerOrActionName, "/")) {//v1.1.2
			$this->redirectUrl($controllerOrActionName);
			return;
		}
		$path = $this->path($controllerOrActionName, $actionName, $params);
		$this->redirectUrl($path);
	}
	
	/**
	 * 在当前界面绘制新的动作
	 * 
	 * 并且不打断当前动作的执行
	 * 
	 * 支持:
	 * - forward(action)
	 * - forward(controller, action)
	 *
	 * @param string $controllerOrActionName 控制器或者action名称
	 * @param string $actionName action名称
	 * @param array $params 参数
	 * @since 1.0
	 */
	function forward($controllerOrActionName, $actionName = null, array $params = null) {
		if (is_null($controllerOrActionName) && is_null($actionName)) {
			return;
		}
		if (is_null($actionName)) {
			$controllerName = $this->name;
			$actionName = $controllerOrActionName;
		}
		else {
			$controllerName = $controllerOrActionName;
		}
		$controllerName = $this->_parseControllerPath($this->name(), $controllerName);
		$controller = self::objByName($controllerName);
		if (!$controller) {
			throw new IActionNotFoundException("can not find controller named by '{$controllerName}'", 0x100039);
		}
		$controller->setAction($actionName);
		if ($params) {
			$controller->addParams($params);
		}
		$controller->addParams($this->params);
		$controller->setRouter($this->router);
		$controller->setName($controllerName);
		$controller->exec();
	}
	
	/**
	 * 初始化控制器
	 * 
	 * 请注意：这时候尚且得不到动作的名字
	 *
	 * @since 1.0
	 */
	protected function _init() {
		
	}
	
	/**
	 * 动作执行之前的回调函数
	 *
	 * @since 1.0
	 */
	protected function _before() {
		
	}
	
	/**
	 * 动作执行之后的回调函数
	 * 
	 * @since 1.0
	 */
	protected function _after() {
		
	}
	
	/**
	 * 结束控制器
	 *
	 * @since 1.0
	 */
	protected function _destroy() {
		
	}
	
	/**
	 * 取得区域化对象
	 *
	 * @return ILocale
	 * @since 1.0
	 */
	function locale() {
		if (!$this->locale) {
			$this->locale = IApplication::singleton()->locale();
		}
		return $this->locale;
	}
	
	/**
	 * 取得路由
	 *
	 * @return IRouter
	 * @since 1.0
	 */
	function router() {
		return $this->router;
	}
	
	/**
	 * 添加参数的错误信息
	 *
	 * @param string|array $param 参数名或者多个参数对应的错误信息
	 * @param string $message 错误信息
	 * @since 1.0
	 */
	function addError($param, $message = null) {
		if (is_array($param)) {
			$this->errors = array_merge($this->errors, $param);
			return;
		}
		if (is_array($message) && empty($message)) {
			return;
		}
		if (!isset($this->errors[$param])) {
			$this->errors[$param] = array();
		}
		if (!is_array($message)) {
			$this->errors[$param][] = preg_match("/^%\{(.+)\}$/", $message, $match) ? l($match[1]) : $message;
		}
		else {
			$this->errors[$param] = if_array_join($this->errors[$param], $message);
		}
	}
	
	/**
	 * 取得错误信息
	 *
	 * @param string $name 属性名，如为null，则取全部的错误信息
	 * @return array
	 * @since 1.0
	 */
	function errors($name = null) {
		if (is_null($name)) {
			return $this->errors;
		}
		return isset($this->errors[$name])?$this->errors[$name]:array();
	}
	
	/**
	 * 判断是否有错误信息
	 *
	 * @return boolean
	 * @since 1.0
	 */
	function hasErrors() {
		return !empty($this->errors);
	}
	
	/**
	 * 取得当前对象的属性
	 *
	 * @return array
	 * @since 1.0
	 */
	function attrs() {
		return get_object_vars($this);
	}
	
	/**
	 * 取得控制器类文件
	 *
	 * @param boolean $real 是否处理成和当前操作系统目录分隔符一样的路径
	 * @return string
	 * @since 1.0
	 */
	function file($real = false) {
		return $real ? realpath($this->file) : $this->file;
	}
	
	/**
	 * 显示视图
	 * 
	 * 可以在当前动作中显示一个模板的内容，如：
	 * <code>
	 * if ($condition) {
     *		$this->display("a/b/tpl1", "tpl");
	 *	}
	 * else {
	 *   	$this->display("a/b/tpl2", "tpl");
	 * }
	 * return true;//让和当前动作默认对应的模板不显示
	 * </code>
	 * 
	 * 如果不传入任何参数，则试图显示默认的视图：
	 * <code>
	 * $this->display(); // 试图显示 templates/controller/action.tpl
	 * </code>
	 *
	 * @param string|null|IView $tplOrView 模板名或视图对象（实现IView接口）
	 * @param string|null $ext 扩展名
	 * @since 1.0
	 */
	function display($tplOrView = null, $ext = null) {
		if (is_object($tplOrView) && if_is_instance_of($tplOrView, "IView")) {
			$this->putView($tplOrView);
			$tplOrView->display();
			return;
		}
		
		$defaultView = o("@view.default");
		if (!$defaultView) {
			return;
		}
		$this->displayView($defaultView, $tplOrView, $ext);
	}
	
	/**
	 * 显示某种方案实现的视图
	 * 
	 * 支持：
	 * - displaySmarty($tpl, $ext)
	 *
	 * @param string $view 视图名
	 * @param string|null $tpl 模板名
	 * @param string|null $ext 扩展名
	 * @param array $params 模板中能使用的参数(v1.0.3)
	 * @since 1.0
	 */
	function displayView($view, $tpl = null, $ext = null, array $params = array()) {
		$opts = o("@view.{$view}");
		if (is_array($opts) && isset($opts["class"])) {
			$class = $opts["class"];
			if (class_exists($class) && is_subclass_of($class, "ITplView")) {
				$view = new $class($tpl, $ext);
				$this->putView($view);
				$view->put($params);
				$view->display();
			}
		}
		else {
			throw new IException("unsupported view '{$view}'", 0x100004);
		}
	}
	
	/**
	 * 给视图对象添加值
	 *
	 * @param IView $view 视图对象
	 * @since 1.0
	 */
	private function putView(&$view) {
		//加载配置
		$opts = o("@view." . $view->name());
		$view->init($opts);
		if ($this->viewDirectory) {
			$view->setOpt("template_dir", $this->viewDirectory);
		}
		
		$view->setController($this);
		
		$view->put(array(
			"_params" => $this->request()->params(),
			"_context" => $this->context(),
			"_x" => $this->context(),
//			"_consts" => get_defined_constants(),//出于效率的考虑，暂时注释
			"_controller" => $this,
			"_action" => $this->action(),
			"_app" => IApplication::singleton(),
			"_errors" => $this->errors,
			"_post" => $this->request()->isPost()
		));
		if ($this->router()) {
			$view->put(array(
				"_base" => __BASE__,
				"_router" => __ROUTER__
			));
		}
		$view->put($this->attrs());
	}
	
	/**
	 * 魔术方法
	 * 
	 * 用来支持displayXxx()方法，比如：
	 * - displaySmarty("my", "tpl);
	 * 
	 * 子类重载该方法时必须调用：
	 * <code>
	 *  parent::__call($name, $params);
	 * </code>
	 *
	 * @param string $name 方法名称
	 * @param array $params 参数
	 */
	function __call($name, $params) {
		if (preg_match("/^display([a-z0-9_]+)$/i", $name, $match)) {
			array_unshift($params, if_name_to_c($match[1]));
			call_user_func_array(array($this, "displayView"), $params);
		}
		else {
			throw new IException("Fatal error: Call to undefined method " . __CLASS__ . "::" . $name . "()", 0x100005);

		}
	}
	
	/**
	 * 当前提交的表单对象
	 * 
	 * 可以根据 $formId 来处理某个特别的表单：
	 * <code>
	 * if ($this->form("my-form")->isValid()) {
	 * 	echo "submited successfully";
	 * }
	 * if ($this->form("my-form2")->isValid()) {
	 * 	echo "submited successfully";
	 * }
	 * </code>
	 *
	 * @return UIForm
	 * @since 1.1.0
	 */
	function form($formId = null) {
		$form = $this->form;
		if ($formId) {
			$form->setValid($formId == $form->id());
		}
		return $form;
	}
	
	/**
	 * 设置当前控制器的视图查找路径
	 *
	 * @param string $viewDirectory 视图查找路径
	 * @since 1.1.2
	 */
	function setViewDirectory($viewDirectory) {
		$this->viewDirectory = $viewDirectory;
	}
	
	/**
	 * 取得当前控制器的视图查找路径
	 * 
	 * 如果调用的是默认的controllers/下的控制器，而且没有手工 setViewDirectory()，那么该值为空
	 *
	 * @return string
	 * @since 1.1.2
	 */
	function viewDirectory() {
		return $this->viewDirectory;
	}
}


?>